<template>
  <div
    :class="classes"
    ref="scroller"
    @touchstart="touchStart"
    @touchmove="touchMove"
    @touchend="touchEnd"
  >
    <div class="nut-infinite-top" ref="refreshTop" :style="getStyle">
      <div class="top-box">
        <i class="nut-icon-am-rotate nut-icon-am-infinite"
          ><Refresh class="top-img"></Refresh
        ></i>
        <div class="top-text">刷新中</div>
      </div>
    </div>

    <div class="nut-infinite-container">
      <slot></slot>
    </div>

    <div class="nut-infinite-bottom">
      <template v-if="isInfiniting">
        <div class="bottom-box">
          <slot name="loading">
            <Loading
              class="nut-icon-loading nut-infinite__bottom-box__img"
            ></Loading>
            <div class="bottom-text">{{ loadTxt || "加载中" }}</div>
          </slot>
        </div>
      </template>
      <template v-else-if="!hasMore">
        <slot name="finished">
          <div class="tips">{{ loadMoreTxt || "加载更多" }}</div>
        </slot>
      </template>
    </div>
  </div>
</template>
<script lang="ts">
import {
  toRefs,
  reactive,
  computed,
  ref,
  onActivated,
  onDeactivated,
  onMounted,
  onUnmounted,
} from "vue";
import { createComponent } from "./_part/create";
const { componentName, create } = createComponent("infinite-loading");
import "./index.h5.scss";

import { Loading, Refresh } from "@nutui/icons-vue-taro";
import { useTouch } from "@/utils/hooks/useTouch";
import requestAniFrame from "@/utils/raf";

const getScrollTopRoot = (): number => {
  return (
    window.pageYOffset ||
    document.documentElement.scrollTop ||
    document.body.scrollTop ||
    0
  );
};

const InfiniteLoading = create({
  name: "my-infinite-loading",
  props: {
    hasMore: {
      type: Boolean,
      default: true,
    },
    threshold: {
      type: Number,
      default: 200,
    },
    loadTxt: {
      type: String,
      default: "",
    },
    loadMoreTxt: {
      type: String,
      default: "",
    },
    useCapture: {
      type: Boolean,
      default: false,
    },
    isOpenRefresh: {
      type: Boolean,
      default: true,
    },
    useWindow: {
      type: Boolean,
      default: false,
    },
  },
  emits: ["scroll-change", "load-more", "refresh"],
  components: {
    Loading,
    Refresh,
  },
  setup(props, { emit, slots }) {
    const touch: any = useTouch();
    const state = reactive({
      scrollEl: window as Window | HTMLElement | (Node & ParentNode),
      scroller: null as null | HTMLElement,
      refreshTop: null as null | HTMLElement,
      beforeScrollTop: 0,
      isTouching: false,
      isInfiniting: false,
      refreshMaxH: 0,
      y: 0,
      x: 0,
      distance: 0,
    });

    const classes = computed(() => {
      const prefixCls = componentName;
      return {
        [prefixCls]: true,
      };
    });

    const getStyle = computed(() => {
      return {
        height: state.distance < 0 ? `0px` : `${state.distance}px`,
        transitionDuration: state.isTouching ? 0 : `0.2s`,
      };
    });

    const calculateTopPosition = (el: HTMLElement): number => {
      return !el
        ? 0
        : el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement);
    };

    const isScrollAtBottom = () => {
      let offsetDistance = 0;
      let resScrollTop = 0;
      let direction = "down";

      if (props.useWindow) {
        const windowScrollTop = getScrollTopRoot();
        if (state.scroller) {
          offsetDistance =
            calculateTopPosition(state.scroller) +
            state.scroller.offsetHeight -
            windowScrollTop -
            window.innerHeight;
        }
        resScrollTop = windowScrollTop;
      } else {
        const { scrollHeight, clientHeight, scrollTop } =
          state.scrollEl as HTMLElement;

        offsetDistance = scrollHeight - clientHeight - scrollTop;
        resScrollTop = scrollTop;
      }

      if (state.beforeScrollTop > resScrollTop) {
        direction = "up";
      } else {
        direction = "down";
      }

      state.beforeScrollTop = resScrollTop;

      emit("scroll-change", resScrollTop);

      return offsetDistance <= props.threshold && direction == "down";
    };

    const infiniteDone = () => {
      state.isInfiniting = false;
    };

    const handleScroll = (e) => {
      console.log("handleScroll", e);

      requestAniFrame(() => {
        if (!isScrollAtBottom() || !props.hasMore || state.isInfiniting) {
          return false;
        } else {
          state.isInfiniting = true;
          console.log("load-more");

          emit("load-more", infiniteDone);
        }
      });
    };

    const scrollListener = () => {
      // console.log(state.scrollEl);
      console.log(state.refreshTop);

      state.scrollEl.style["overflow-y"] = "auto";
      state.scrollEl.addEventListener("scroll", handleScroll, props.useCapture);
    };

    const refreshDone = () => {
      state.distance = 0;
      state.isTouching = false;
    };

    const touchStart = async (event: TouchEvent) => {
      touch.start(event);

      if (
        state.beforeScrollTop == 0 &&
        !state.isTouching &&
        props.isOpenRefresh
      ) {
        state.y = event.touches[0].pageY;
        state.isTouching = true;

        const childHeight = (
          (state.refreshTop as HTMLElement)?.firstElementChild as HTMLElement
        )?.offsetHeight;

        state.refreshMaxH = Math.floor(childHeight * 1 + 10);
      }
    };

    const touchMove = (event: TouchEvent) => {
      touch.move(event);

      state.distance = event.touches[0].pageY - state.y;

      if (
        (touch as any).isVertical() &&
        state.distance > 0 &&
        state.isTouching
      ) {
        event.preventDefault();
        if (state.distance >= state.refreshMaxH)
          state.distance = state.refreshMaxH;
      } else {
        state.distance = 0;
        state.isTouching = false;
      }
    };

    const touchEnd = () => {
      if (state.distance) {
        if (state.distance < state.refreshMaxH) {
          state.distance = 0;
        } else {
          emit("refresh", refreshDone);
        }
      }

      setTimeout(() => {
        touch.reset();
      }, 0);
    };

    // 滚动监听对象
    const getParentElement = (el: HTMLElement) => {
      return el && el.parentNode;
    };

    const removeScrollListener = () => {
      state.scrollEl.removeEventListener(
        "scroll",
        handleScroll,
        props.useCapture
      );
    };

    onMounted(() => {
      const parentElement = getParentElement(
        state.scroller as HTMLElement
      ) as Node & ParentNode;
      state.scrollEl = props.useWindow ? window : parentElement;

      scrollListener();
    });

    onUnmounted(() => {
      removeScrollListener();
    });

    const isKeepAlive = ref(false);

    onActivated(() => {
      if (isKeepAlive.value) {
        isKeepAlive.value = false;
        scrollListener();
      }
    });

    onDeactivated(() => {
      isKeepAlive.value = true;
      removeScrollListener();
    });

    return {
      classes,
      ...toRefs(state),
      touchStart,
      touchMove,
      touchEnd,
      getStyle,
      slots,
    };
  },
});

export default InfiniteLoading;
</script>
